package captcha

import (
	"bytes"
	"encoding/hex"
	"math/rand"
	"time"

	"github.com/fogleman/gg"
	"github.com/golang/freetype/truetype"
	"golang.org/x/image/font"
)

/*
图片验证码生成
*/

// RandomKey 生成验证码
// length: 生成随机字符串长度
func RandomKey(length, level int) (strPol string) {
	var slicePol []byte
	var str string
	switch level {
	case 1:
		str = "0123456789"
	case 2:
		str = "ABCDEFGHIJKMNPQRSTUVWXYZabcdefghijkmnpqrstuvwxyz0123456789"
	default:
		str = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
	}
	rand.Seed(time.Now().UnixNano())
	for i := 0; i < length; i++ {
		n := rand.Intn(len(str))
		slicePol = append(slicePol, str[n])
	}
	strPol = string(slicePol)
	return
}

// GenerateImageByte 根据传入的字符生成图片验证码
// width:图片的宽
// height:图片的高
// text:字符内容
func GenerateImageByte(width, height int, text string) (b []byte, err error) {
	dc := gg.NewContext(width, height)
	return imageByText(dc, width, height, text)
}

func GenerateDefaultImageByte() (b []byte, code string, err error) {
	width, height := 200, 50
	code = RandomKey(6, 0)
	b, err = GenerateImageByte(width, height, code)
	if err != nil {
		return nil, "", err
	}
	return
}

// GenerateImageSaveFile 根据传入的字符生成图片验证码,保存到指定文件
// width:图片的宽
// height:图片的高
// text:字符内容
// path:文件保存路径
func GenerateImageSaveFile(width, height int, text string, path string) (err error) {
	dc := gg.NewContext(width, height)
	_, err = imageByText(dc, width, height, text)
	if err != nil {
		return err
	}
	err = dc.SavePNG(path)
	if err != nil {
		return err
	}
	return nil
}

func GenerateDefaultImageSaveFile(path string) (code string, err error) {
	width, height := 200, 50
	code = RandomKey(6, 0)
	err = GenerateImageSaveFile(width, height, code, path)
	if err != nil {
		return "", err
	}
	return
}

//  根据传入的字符生成图片验证码 width:图片的宽 height:图片的高
func imageByText(dc *gg.Context, width, height int, text string) (b []byte, err error) {
	textLen := len(text)
	bgR, bgG, bgB, bgA := getRandColorRange(240, 255)
	dc.SetRGBA255(bgR, bgG, bgB, bgA)
	dc.Clear()

	// 干扰线
	for i := 0; i < 10; i++ {
		x1, y1 := getRandPos(width, height)
		x2, y2 := getRandPos(width, height)
		r, g, b, a := getRandColor(255)
		w := float64(rand.Intn(3) + 1)
		dc.SetRGBA255(r, g, b, a)
		dc.SetLineWidth(w)
		dc.DrawLine(x1, y1, x2, y2)
		dc.Stroke()
	}

	fontSize := float64(height/2) + 5
	face, err := loadFontFace(fontSize)
	if err != nil {
		return nil, err
	}
	dc.SetFontFace(face)

	for i := 0; i < len(text); i++ {
		r, g, b, _ := getRandColor(100)
		dc.SetRGBA255(r, g, b, 255)
		fontPosX := float64(width/textLen*i) + fontSize*0.8

		writeText(dc, text[i:i+1], fontPosX, float64(height/2))
	}
	buffer := bytes.NewBuffer(nil)
	err = dc.EncodePNG(buffer)
	if err != nil {
		return nil, err
	}
	b = buffer.Bytes()
	return
}

// 渲染文字
func writeText(dc *gg.Context, text string, x, y float64) {
	xfload := -10 - rand.Float64()*5 + x
	yfload := -rand.Float64()*5 + y

	radians := 40 - rand.Float64()*80
	dc.RotateAbout(gg.Radians(radians), x, y)
	dc.DrawStringAnchored(text, xfload, yfload, 0.2, 0.5)
	dc.RotateAbout(-1*gg.Radians(radians), x, y)
	dc.Stroke()
}

// 随机坐标
func getRandPos(width, height int) (x float64, y float64) {
	x = rand.Float64() * float64(width)
	y = rand.Float64() * float64(height)
	return x, y
}

// 随机颜色
func getRandColor(maxColor int) (r, g, b, a int) {
	r = int(uint8(rand.Intn(maxColor)))
	g = int(uint8(rand.Intn(maxColor)))
	b = int(uint8(rand.Intn(maxColor)))
	a = int(uint8(rand.Intn(255)))
	return r, g, b, a
}

// 随机颜色范围
func getRandColorRange(miniColor, maxColor int) (r, g, b, a int) {
	if miniColor > maxColor {
		miniColor = 0
		maxColor = 255
	}
	r = int(uint8(rand.Intn(maxColor-miniColor) + miniColor))
	g = int(uint8(rand.Intn(maxColor-miniColor) + miniColor))
	b = int(uint8(rand.Intn(maxColor-miniColor) + miniColor))
	a = int(uint8(rand.Intn(maxColor-miniColor) + miniColor))
	return r, g, b, a
}

// 加载字体
func loadFontFace(points float64) (font.Face, error) {

	fontBytes, err := hex.DecodeString(TTFByte)
	if err != nil {
		return nil, err
	}

	f, err := truetype.Parse(fontBytes)
	if err != nil {
		return nil, err
	}
	face := truetype.NewFace(f, &truetype.Options{
		Size: points,
	})
	return face, nil
}
